﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using RrslLite.Net.Messages;

#if(SILVERLIGHT)
using System.Windows;
#endif


namespace RrslLite.Net.Attributes
{
    public static class KnownTypeProvider
    {
        private static List<Type> _knownTypes = new List<Type>();

        private static readonly object SyncRoot = new object();

        public static void ClearAllKnownTypes()
        {
            _knownTypes = new List<Type>();
        }

        public static void Register<T>()
        {
            Register(typeof(T));
        }

        public static void Register(Type type)
        {
            _knownTypes.Add(type);

            _knownTypes.Add(Type.GetType(
                string.Format(
                    "System.Collections.Generic.List`1[[{0}]]",
                    type.AssemblyQualifiedName)));

        }

        public static void RegisterDerivedTypesOf<T>(Assembly assembly)
        {
            RegisterDerivedTypesOf(typeof(T), assembly.GetTypes());
        }

        public static void RegisterDerivedTypesOf<T>(IEnumerable<Type> types)
        {
            RegisterDerivedTypesOf(typeof(T), types);
        }

        public static void RegisterDerivedTypesOf(Type type, Assembly assembly)
        {
            RegisterDerivedTypesOf(type, assembly.GetTypes());
        }

        public static void RegisterModelDomainNamespace(string modelDomainNamespace)
        {
            RegisterDataContracts(modelDomainNamespace);
        }

        public static void RegisterDerivedTypesOf(Type type, IEnumerable<Type> types)
        {
            List<Type> derivedTypes = GetDerivedTypesOf(type, types);
            _knownTypes = Union(_knownTypes, derivedTypes);
            _knownTypes.Union(types);
        }

        public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
        {
            lock (SyncRoot)
            {
                Register<Message>();

                if (provider == null)
                {
                    return _knownTypes;
                }

                var serviceKnownTypeProviderAttributes
                    =
                    provider.GetCustomAttributes(true).Where(
                        predicate => predicate.GetType() == typeof(ServiceKnownTypeProviderAttribute));

                foreach (ServiceKnownTypeProviderAttribute serviceKnownTypeProviderAttribute in serviceKnownTypeProviderAttributes)
                {
                    RegisterModelDomainNamespace(serviceKnownTypeProviderAttribute.ModelDomainNamespace);
                }

                return _knownTypes;
            }
        }

        private static List<Type> GetDerivedTypesOf(Type baseType, IEnumerable<Type> types)
        {
            return types.Where(t => !t.IsAbstract && t.IsSubclassOf(baseType)).ToList();
        }

        private static List<T> Union<T>(IEnumerable<T> first, IEnumerable<T> second)
        {
            return first.Union(second).ToList();
        }

        private static List<String> RegisteredModelDomainNamespace = new List<string>();

#if(!SILVERLIGHT)
        private static void RegisterDataContracts(string modelDomainNamespace)
        {
            if (RegisteredModelDomainNamespace.Contains(modelDomainNamespace))
            {
                return;
            }

            RegisteredModelDomainNamespace.Add(modelDomainNamespace);

            // get all relevant assemblies
            var assemblies =
                AppDomain.CurrentDomain.GetAssemblies().Where(
                    assembly => assembly.FullName.ToLowerInvariant().StartsWith(modelDomainNamespace)).ToList();

            if (assemblies.Count() == 0)
            {
                try
                {
                    assemblies.Add(AppDomain.CurrentDomain.Load(new AssemblyName(modelDomainNamespace)));
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }

            // get all public types
            var publicTypes = assemblies.SelectMany(assembly => assembly.GetExportedTypes()).ToList();

            var listTypes = from exportedType in publicTypes
                            let listTypeName = string.Format(
                                "System.Collections.Generic.List`1[[{0}]]",
                                exportedType.AssemblyQualifiedName)
                            select Type.GetType(listTypeName);

            //provide proxy types

            //var proxyTypes = 

            var result = publicTypes.Concat(listTypes);

            RegisterDerivedTypesOf<object>(result);
        }
#endif

#if(SILVERLIGHT)
        public static void RegisterDataContracts(string modelDomainNamespace)
        {
            modelDomainNamespace = modelDomainNamespace.ToLower();

            if (RegisteredModelDomainNamespace.Contains(modelDomainNamespace))
            {
                return;
            }

            RegisteredModelDomainNamespace.Add(modelDomainNamespace);

            // get all relevant assemblies
            var assemblies = new List<Assembly>();
            foreach (AssemblyPart ap in Deployment.Current.Parts)
            {
                var streamResourceInfo = Application.GetResourceStream(new Uri(ap.Source, UriKind.Relative));
                var assembly = new AssemblyPart().Load(streamResourceInfo.Stream);
                if (assembly.FullName.ToLower().StartsWith(modelDomainNamespace))
                    assemblies.Add(assembly);
            }

            // get all public types
            var publicTypes = assemblies.SelectMany(assembly => assembly.GetExportedTypes()).ToList();

            var listTypes = from exportedType in publicTypes
                            where exportedType.IsClass
                            let listTypeName = string.Format(
                                "System.Collections.Generic.List`1[[{0}]]",
                                exportedType.AssemblyQualifiedName
                                )
                            select Type.GetType(listTypeName);

            var result = publicTypes.Concat(listTypes);

            KnownTypeProvider.RegisterDerivedTypesOf<object>(result);
        }
#endif
    }
}
